<?php
define("POS_EAST",  1);
define("POS_SOUTH", 2);
define("POS_NORTH", 3);
define("POS_WEST",  4);
class Animation{
    private $backgrounds;
    private $foregrounds;
    //
    private $background;
    //composited background
    private $foreground;
    //composited foreground
    private $rasters;
    private $annotations;
    private $images;
    private $legend;
    private $titlebar;
    private $area;
    private $bgColor = "transparent";    
    private $fps = 2;
    private $outputAni = "animate_output.gif";
    private $delay = 50;//animation image delay between each
    //
    private function compositeBackgrounds(){
        $count = count($this->backgrounds);
        if($count > 0){
            try{
                $image = new Imagick($this->backgrounds[0][0]); //file path = backgrounds[0][0]
                if($count > 1){
                    for($idx = 1; $idx < $count; $idx++){
                        $over = new Imagick($this->backgrounds[$idx][0]);
                        $image->compositeImage($over, Imagick::COMPOSITE_DISSOLVE, $x = 0, $y = 0);
                    }
                }
                $this->background = $image;
            }catch(ImagickException $e){
                echo "error: $e";
            }
        }
    }
    private function compositeForegrounds(){
        $count = count($this->foregrounds);
        if($count > 0){
            try{
                $image = new Imagick($this->foregrounds[0][0]); //file path = foregrounds[0][0]
                if($count > 1){
                    for($idx = 1; $idx < $count; $idx++){
                        $over = new Imagick($this->foregrounds[$idx][0]);
                        $image->compositeImage($over, Imagick::COMPOSITE_DISSOLVE, $x = 0, $y = 0);
                    }
                }
                $this->foreground = $image;
            }catch(ImagickException $e){
                echo "error: $e";
            }
        }
    }
    private function prepareArea(){
        //do some check;
        $this->checkComponent();
        $legendWidth = $legendHeight = $bgX = $bgY = $legendX = $legendY = 0;
        switch($this->legend->position){
            case POS_EAST:
                $legendWidth = $this->legend->im->getImageWidth();
                $legendX = $this->getBackground()->getImageWidth();
                $legendY = $this->titlebar->height;
                break;
            case POS_SOUTH:
                $legendHeight = $this->legend->im->getImageHeight();
                $legendY = $this->getBackground()->getImageHeight();
                break;
            case POS_WEST:
                $bgX = $legendWidth = $this->legend->im->getImageWidth();
                break;
            case POS_NORTH:
                $bgY = $legendHeight = $this->legend->im->getImageHeight();
                break;
        }

        $background = $this->getBackground();
        $bgY += $this->titlebar->height;
        $width = $background->getImageWidth() + $legendWidth;
        $height = $background->getImageHeight() + $legendHeight;
        $this->area = new Imagick();
        $this->area->newImage($width, $height, $bg = new ImagickPixel($this->bgColor), "png");
        //composite background
        $this->area->compositeImage($background, Imagick::COMPOSITE_DISSOLVE, $bgX, $bgY);
        $this->background->bgX = $bgX;
        $this->background->bgY = $bgY;
        //composite legend
        $this->area->compositeImage($this->legend->im, Imagick::COMPOSITE_DISSOLVE, $legendX, $legendY);
        //composite titlebar
        if($this->titlebar && $this->titlebar->im){
            $this->titlebar->im->resizeImage($this->getArea()->getImageWidth(), $this->titlebar->im->getImageHeight(), IMAGICK::FILTER_UNDEFINED, 1);
            $this->area->compositeImage($this->titlebar->im, Imagick::COMPOSITE_DISSOLVE, 0, 0);
        }
        //adding annotations
        if(count($this->annotations)){
            foreach($this->annotations as $annotate){
                $this->area->annotateImage(new ImagickDraw(), $annotate->x, $annotate->y, $angle = 0, $annotate->text);
            }
        }
        //adding images
        if(count($this->images)){
            foreach($this->images as $image){
                $this->area->compositeImage($image->im, Imagick::COMPOSITE_DISSOLVE, $image->x, $image->y);
            }
        }
    }
    function setBackgroundColor($color){
        if($color) $this->bgColor = $color;
    }
    private function checkComponent(){
        
    }
    /**
     *
     * @param string $filesrc the legend file path
     * @param int $position
     */
    public function addLegend($filesrc, $position = POS_EAST){
        $this->legend->im = new Imagick($filesrc);
        $this->legend->position = $position;
    }
    /**
     *
     * @param string $filesrc
     */
    public function addTitlebar($filesrc){
        $this->titlebar->im = new Imagick($filesrc);
        $this->titlebar->height = $this->titlebar->im->getImageHeight();
    }
    /**
     *
     * @param int $height
     */
    public function setTitleHeight($height){
        $this->titlebar->height = $height;
    }
    /**
     *
     * @param string $filesrc
     * @param int $x
     * @param int $y
     */
    public function addImage($filesrc, $x, $y){
        $newimage->im = new Imagick($filesrc);
        $newimage->x = $x;
        $newimage->y = $y;
        $this->images[] = $newimage;
    }
    /**
     *
     * @param <type> $delay its unit is micro-second
     */
    function setDelay($delay){
        $this->delay = $delay;
        $this->fps = round(100 / $this->delay);
    }
    /**
     *
     * @param string $ffmpeg the ffmpeg's full path
     * @param string $output output file name
     */
    function makeWmv($ffmpeg = "", $output = ""){
        //generate tmp images

        $count = $this->getRastersCount();
        echo $count."\n";
        for($idx = 0; $idx < $count; $idx++){
            $tmp = substr(10001 + $idx, 1);
            $filename = "imagetmp/tmp_{$tmp}.jpg";
            echo $filename.".\n";
            $this->saveFrame($idx, $filename);
        }

        $input = dirname(__FILE__)."/imagetmp/tmp_%4d.jpg";
        if(!$output )$output = $output ? $output : "animation_output.wmv";
        $cmd = "ffmpeg -r $this->fps -f image2 -i $input $output";
        echo $cmd."\n";
        @exec($cmd);
    }
    /**
     *
     * @param string $text
     * @param int $x
     * @param int $y
     */
    public function addAnnotate($text, $x, $y, $color){
        $newAnnotat->color = $color;
        $newAnnotat->text = $text;
        $newAnnotat->x = $x;
        $newAnnotat->y = $y;
        $this->annotations[] = $newAnnotat;
    }
    /**
     *
     * @param array $rasters
     * @param int $annotateX
     * @param int $annotateY
     */
    public function addRaster($rasters, $annotateX, $annotateY){
        $this->sortByItem($rasters, $sortField = 1);
        $this->rasters->im = $rasters;
        $this->rasters->annotateX = $annotateX;
        $this->rasters->annotateY = $annotateY;
    }
    /**
     *
     * @param array $backgrounds
     */
    public function addBackground($backgrounds){
        $this->sortByItem($array);
        $this->backgrounds = $backgrounds;
    }
    /**
     *
     * @param array $foregrounds
     */
    public function addForeground($foregrounds){
        $this->sortByItem($foregrounds);
        $this->foregrounds = $foregrounds;
    }
    /**
     * sort a 2d array by the sub array item's value
     * @param array $array
     * @param int $sidx the sub array item index
     */
    static function sortByItem(&$array, $sidx = 0){//sort index
        if($array){
            for($len1 = count($array), $idx1 = 0; $idx1 < $len1; $idx1++){
                $tmpIdx = $idx1;
                for($idx2 = $idx1; $idx2 < $len1; $idx2++){
                    if($array[$tmpIdx][$sidx] > $array[$idx2][$sidx]){
                        $tmpIdx = $idx2;
                    }
                }
                if($tmpIdx != $idx1){
                    $tmp = $array[$idx1];
                    $array[$idx1] = $array[$tmpIdx];
                    $array[$tmpIdx] = $tmp;
                }
            }
        }
    }
    /**
     * read file list from prepared listfile to array
     * the array structure is array(array(filepath,parameter));
     * @param string $listFilePath
     * @param bool $firstHeader
     * @return array
     */
    static function readListFile($listFilePath, $firstHeader=FALSE){
        $list = array();
        if(is_file($listFilePath)){
            $handle = fopen($listFilePath, "r");
            $row = 1;
            while($item = fgetcsv($handle)){
                if($firstHeader && $row++ == 1){
                    continue;
                }
                $filepath = trim($item[0]);
                //using "#" begin for comment out it;
                if(substr($filepath, $start = 0, $length = 1) != "#"){
                    if(!file_exists($filepath)){
                        $this->errors["checkfiles"][] = "file".SP.$filepath.SP."doesnot exist.";
                    }else{
                        $list[] = $item;
                    }
                }
            }
            fclose($handle);
        }
        return $list;
    }
    function getArea(){
        if(!$this->area){
            $this->prepareArea();
        }
        return $this->area->clone();//the clone is very important, because its output will be changed, if not clone() it will changed for other usage.
    }
    function getBackground(){
        if(!$this->background){
            $this->compositeBackgrounds();
        }
        return $this->background;
    }
    function getForeground(){
        if(!$this->foreground){
            $this->compositeForegrounds();
        }
        return $this->foreground;
    }
    /**
     *
     * @return int
     */
    function getRastersCount(){
        if(!$this->rastersCount){
            $this->rastersCount = count($this->rasters->im);
        }
        return $this->rastersCount;
    }
    /**
     * get frame of some index
     * @param int $idx
     * @return Imagick
     */
    function getFrame($idx, $width = "", $height = ""){
        $rastersCount = $this->getRastersCount();
        if($rastersCount && $idx >= 0 && $idx < $rastersCount){
            //adding background with static images and annotation
            $frame = $this->getArea();
            //composite background, raster, and foreground
            $rasterFile = $this->rasters->im[$idx][0];
            $raster = new Imagick($rasterFile);
            //transparent
            $raster->paintTransparentImage("#000000", $alpha = 0.0, $fuzz = 0);
            $raster->resizeImage($this->getBackground()->getImageWidth(), $this->getBackground()->getImageHeight(), Imagick::FILTER_POINT, $blurry = 1);
            //composite need transparency
            $raster->setImageOpacity($alpha = 0.4);
            $frame->compositeImage($raster, Imagick::COMPOSITE_DEFAULT, $this->getBackground()->bgX, $this->getBackground()->bgY, IMAGICK::CHANNEL_ALL);

            //@todo make by need refactor for other dynamic annotation
            //adding dynamic annotation from rasters configure
            $text = $this->rasters->im[$idx][1];
            $draw = new ImagickDraw();
            $frame->annotateImage($draw, $this->rasters->annotateX, $this->rasters->annotateY, $angle = 0, $text);

            //adding foreground
            $foreground = $this->getForeground();
            $frame->compositeImage($foreground, Imagick::COMPOSITE_DISSOLVE, $this->getBackground()->bgX, $this->getBackground()->bgY, IMAGICK::CHANNEL_ALL);
            return $frame;
        }else{
            return false;
        }
    }
    /**
     * save one frame to file with some filetype
     * @param int $idx
     * @param string $filename
     */
    function saveFrame($idx, $filename=""){
        $im = $this->getFrame($idx);
        if(!$filename) $filename = "animationFrame_{$idx}.png";
        $im->writeImage($filename);
    }
    /**
     * genereate animation gif
     */
    function make($width = "", $height = "", $output = "", $makeFormat=""){
        $aniGif = new Imagick();
        $count = $this->getRastersCount();
        for($idx = 0; $idx < $count; $idx++){
            $frame = $this->getFrame($idx, $width, $height);
            if($frame){
                $aniGif->addImage($frame);
                $aniGif->setImageDelay($this->delay);
            }
        }
        $this->outputAni = $output ? $output : $this->outputAni;
        $aniGif->writeImages($this->outputAni, $adjure = true);
    }
}
